{
  "cells": [
    {
      "cell_type": "markdown",
      "source": [
        "# Efficient Coding Assistant with simpleaichat\n",
        "\n",
        "_Updated using `gpt-3.5-turbo-0613`_\n",
        "\n",
        "Many coders use ChatGPT for coding help, however the web interface can be slow and contain unnecessary discussion when you want code. With some system prompt engineering and simplechatapi streaming, you can cut down code time generation and costs significantly.\n",
        "\n",
        "**DISCLAIMER: Your mileage may vary in terms of code quality and accuracy in practice, but this is a good, hackable starting point.**\n",
        "\n"
      ],
      "metadata": {
        "id": "-jfTDBnMbGO3"
      }
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {
        "id": "5ZeSKyedacCE"
      },
      "outputs": [],
      "source": [
        "!pip install -q simpleaichat\n",
        "\n",
        "from simpleaichat import AIChat\n",
        "from getpass import getpass"
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "For the following cell, input your OpenAI API key when prompted. **It will not be saved to the notebook**."
      ],
      "metadata": {
        "id": "1kXTv7zXapit"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "api_key = getpass(\"OpenAI Key: \")"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "e_3QIHtnaqdw",
        "outputId": "334508c7-d1da-4270-b98d-359da120ceff"
      },
      "execution_count": 2,
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "OpenAI Key: ··········\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "metadata": {
        "id": "mxBkiR9FacCF"
      },
      "outputs": [],
      "source": [
        "params = {\"temperature\": 0.0}  # for reproducibility\n",
        "model = \"gpt-3.5-turbo\"  # in production, may want to use model=\"gpt-4\" if have access\n",
        "\n",
        "ai = AIChat(api_key=api_key, console=False, params=params, model=model)"
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "Let's start with a simple `is_palindrome()` function in Python, and track how long it takes to run. The output of this should be similar to what is shown in the ChatGPT webapp."
      ],
      "metadata": {
        "id": "-4eVxf3Jawo_"
      }
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "SCkboiHsacCG",
        "outputId": "1608a19a-23e2-4555-c096-98d5bd08f51f"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Sure! Here's an example of an is_palindrome() function in Python:\n",
            "\n",
            "```python\n",
            "def is_palindrome(word):\n",
            "    # Convert the word to lowercase and remove any spaces\n",
            "    word = word.lower().replace(\" \", \"\")\n",
            "    \n",
            "    # Check if the word is equal to its reverse\n",
            "    if word == word[::-1]:\n",
            "        return True\n",
            "    else:\n",
            "        return False\n",
            "```\n",
            "\n",
            "You can use this function to check if a word is a palindrome. It will return True if the word is a palindrome and False otherwise.\n",
            "\n",
            "\n",
            "\n",
            "CPU times: user 23.7 ms, sys: 1.32 ms, total: 25 ms\n",
            "Wall time: 2.49 s\n"
          ]
        }
      ],
      "source": [
        "%%time\n",
        "response = ai(\"Write an is_palindrome() function in Python.\")\n",
        "print(response)\n",
        "print(\"\\n\\n\")  # separate time from generated text for readability"
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "That's the typical implementation. However, there's a trick to cut the processing time in half, well known by technical hiring managers who want to trip up prospective candidates.\n",
        "\n",
        "ChatGPT outputs the statistically most common implementation, but it's not necessairily the best. A second pass allows ChatGPT to refine its output."
      ],
      "metadata": {
        "id": "dBOCM4LMbz3C"
      }
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "BwsfrcPHacCH",
        "outputId": "dcbfdce5-8f72-4d00-cfe9-6eef41018639"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Certainly! Here's an optimized version of the is_palindrome() function that uses two pointers to check if a word is a palindrome:\n",
            "\n",
            "```python\n",
            "def is_palindrome(word):\n",
            "    # Convert the word to lowercase and remove any spaces\n",
            "    word = word.lower().replace(\" \", \"\")\n",
            "    \n",
            "    # Initialize two pointers, one at the start and one at the end of the word\n",
            "    left = 0\n",
            "    right = len(word) - 1\n",
            "    \n",
            "    # Iterate until the pointers meet in the middle\n",
            "    while left < right:\n",
            "        # If the characters at the pointers are not equal, the word is not a palindrome\n",
            "        if word[left] != word[right]:\n",
            "            return False\n",
            "        \n",
            "        # Move the pointers towards the middle\n",
            "        left += 1\n",
            "        right -= 1\n",
            "    \n",
            "    # If the loop completes without returning False, the word is a palindrome\n",
            "    return True\n",
            "```\n",
            "\n",
            "This optimized version reduces the number of comparisons by using two pointers that start at opposite ends of the word and move towards the middle. It will return True if the word is a palindrome and False otherwise.\n",
            "\n",
            "\n",
            "\n",
            "CPU times: user 30.7 ms, sys: 4.91 ms, total: 35.6 ms\n",
            "Wall time: 4.22 s\n"
          ]
        }
      ],
      "source": [
        "%%time\n",
        "response = ai(\"Make it more efficient.\")\n",
        "print(response)\n",
        "print(\"\\n\\n\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "ZWRKL30uacCH",
        "outputId": "ac47340b-9c7a-4517-a36a-d4fb92765311"
      },
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "511"
            ]
          },
          "metadata": {},
          "execution_count": 6
        }
      ],
      "source": [
        "ai.total_length"
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "In all, it took ~6 seconds and utilized 511 tokens. But there's a lot of unnecessary natter in the output:\n",
        "\n",
        "- The conversational preamble before the code\n",
        "- Docstrings and code comments\n",
        "- A long explanation of the code which may be redundant to the above\n",
        "\n",
        "All this natter adds latency and cost.\n",
        "\n",
        "The easiest technique to guide AI text generation is to use **prompt engineering**, specifically to give it a new system prompt to say precisely what you want. As of June 27th 2023, the default ChatGPT API responds very well to commands.\n",
        "\n",
        "Now, for the new `system` prompt:"
      ],
      "metadata": {
        "id": "okqqoe7lcqUH"
      }
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "metadata": {
        "id": "iA9AASKfacCH"
      },
      "outputs": [],
      "source": [
        "system_optimized = \"\"\"Write a Python function based on the user input.\n",
        "\n",
        "You must obey ALL the following rules:\n",
        "- Only respond with the Python function.\n",
        "- Never put in-line comments or docstrings in your code.\"\"\"\n",
        "\n",
        "ai_2 = AIChat(api_key=api_key, system=system_optimized, model=model, params=params)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "MRYfniAXacCI",
        "outputId": "fecb6928-a1e8-48f6-c336-5055cbca7a38"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "def is_palindrome(word):\n",
            "    return word == word[::-1]\n",
            "\n",
            "\n",
            "\n",
            "CPU times: user 11.6 ms, sys: 4.01 ms, total: 15.6 ms\n",
            "Wall time: 1.04 s\n"
          ]
        }
      ],
      "source": [
        "%%time\n",
        "response = ai_2(\"is_palindrome\")\n",
        "print(response)\n",
        "print(\"\\n\\n\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "lGJr-GfIacCI",
        "outputId": "68a555bd-d3e0-4b39-cca6-a11ce6ad1455"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "def is_palindrome(word):\n",
            "    length = len(word)\n",
            "    for i in range(length // 2):\n",
            "        if word[i] != word[length - i - 1]:\n",
            "            return False\n",
            "    return True\n",
            "\n",
            "\n",
            "\n",
            "CPU times: user 19.7 ms, sys: 1.73 ms, total: 21.5 ms\n",
            "Wall time: 2.64 s\n"
          ]
        }
      ],
      "source": [
        "%%time\n",
        "response = ai_2(\"Make it more efficient.\")\n",
        "print(response)\n",
        "print(\"\\n\\n\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "8eMDXGysacCJ",
        "outputId": "0d6272c1-cb2a-460b-fe37-09c896b119e7"
      },
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "190"
            ]
          },
          "metadata": {},
          "execution_count": 10
        }
      ],
      "source": [
        "ai_2.total_length"
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "~3 seconds total with 190 tokens used: that's 2x faster at 1/3 the cost!"
      ],
      "metadata": {
        "id": "ua28VqKteKkr"
      }
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "SaE3ftkMacCJ"
      },
      "source": [
        "## Create a Function\n",
        "\n",
        "Now we can create a function to automate the two calls we did above for any arbitrary input.\n",
        "\n",
        "For each call, we'll create an independent temporary session within simpleaichat and then clean it up. We'll also use a regex to strip unneded backticks."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "metadata": {
        "id": "mN_-3fwuacCL"
      },
      "outputs": [],
      "source": [
        "from uuid import uuid4\n",
        "import re\n",
        "\n",
        "ai_func = AIChat(api_key=api_key, console=False)\n",
        "def gen_code(query):\n",
        "    id = uuid4()\n",
        "    ai_func.new_session(api_key=api_key, id=id, system=system_optimized, params=params, model=model)\n",
        "    _ = ai_func(query, id=id)\n",
        "    response_optimized = ai_func(\"Make it more efficient.\", id=id)\n",
        "\n",
        "    ai_func.delete_session(id=id)\n",
        "    return response_optimized"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "kIkdpBwOacCL",
        "outputId": "f00c393b-87c0-4bc6-9c35-d323c6de6812"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "def is_palindrome(word):\n",
            "    length = len(word)\n",
            "    for i in range(length // 2):\n",
            "        if word[i] != word[length - i - 1]:\n",
            "            return False\n",
            "    return True\n",
            "\n",
            "\n",
            "\n",
            "CPU times: user 27.8 ms, sys: 1.94 ms, total: 29.8 ms\n",
            "Wall time: 1.96 s\n"
          ]
        }
      ],
      "source": [
        "%%time\n",
        "code = gen_code(\"is_palindrome\")\n",
        "print(code)\n",
        "print(\"\\n\\n\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "S1LjZjFBacCM",
        "outputId": "53d96baf-1f6b-46fb-bba8-5eda772d8c87"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "def reverse_string(string):\n",
            "    return ''.join(reversed(string))\n",
            "\n",
            "\n",
            "\n",
            "CPU times: user 16.4 ms, sys: 644 µs, total: 17 ms\n",
            "Wall time: 1.42 s\n"
          ]
        }
      ],
      "source": [
        "%%time\n",
        "code = gen_code(\"reverse string\")\n",
        "print(code)\n",
        "print(\"\\n\\n\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "VdSNUiapacCM",
        "outputId": "207634e0-ef48-4428-a3c0-15a80ad845f8"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "def pretty_print_dict(dictionary):\n",
            "    import json\n",
            "    print(json.dumps(dictionary, indent=4, separators=(',', ': ')))\n",
            "\n",
            "\n",
            "\n",
            "CPU times: user 17.2 ms, sys: 1.87 ms, total: 19.1 ms\n",
            "Wall time: 1.31 s\n"
          ]
        }
      ],
      "source": [
        "%%time\n",
        "code = gen_code(\"pretty print dict\")\n",
        "print(code)\n",
        "print(\"\\n\\n\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "7ppyZrFTacCM",
        "outputId": "a4e04c60-4681-4d7f-b846-028a9b1271f6"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "import cv2\n",
            "\n",
            "def load_and_flip_image(image_path):\n",
            "    image = cv2.imread(image_path)\n",
            "    flipped_image = cv2.flip(image, 1)\n",
            "    cv2.imwrite(\"flipped_image.jpg\", flipped_image)\n",
            "    return \"flipped_image.jpg\"\n",
            "\n",
            "\n",
            "\n",
            "CPU times: user 21.1 ms, sys: 3.86 ms, total: 24.9 ms\n",
            "Wall time: 2.18 s\n"
          ]
        }
      ],
      "source": [
        "%%time\n",
        "code = gen_code(\"load and flip image horizontally\")\n",
        "print(code)\n",
        "print(\"\\n\\n\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "sNQhAKFqacCM",
        "outputId": "fcfc568f-0777-4df4-a8f7-20fa0e4e34d7"
      },
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "import hashlib\n",
            "from multiprocessing import Pool, cpu_count\n",
            "\n",
            "def multiprocess_hash(data):\n",
            "    def hash_string(string):\n",
            "        return hashlib.sha256(string.encode()).hexdigest()\n",
            "\n",
            "    num_processes = cpu_count()\n",
            "    pool = Pool(processes=num_processes)\n",
            "    hashed_data = pool.map(hash_string, data)\n",
            "    pool.close()\n",
            "    pool.join()\n",
            "\n",
            "    return hashed_data\n",
            "\n",
            "\n",
            "\n",
            "CPU times: user 28.8 ms, sys: 3.82 ms, total: 32.6 ms\n",
            "Wall time: 3.31 s\n"
          ]
        }
      ],
      "source": [
        "%%time\n",
        "code = gen_code(\"multiprocess hash\")\n",
        "print(code)\n",
        "print(\"\\n\\n\")"
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "## Generating Optimized Code in a Single API Call w/ Structured Output Data\n",
        "\n",
        "Sometimes you may not want to make two calls to OpenAI. One hack you can do is to define an expected structured output to tell it to sequentially generate the normal code output, then the optimized output.\n",
        "\n",
        "This structure is essentially a different form of prompt engineering, but you can combine it with a system prompt if needed.\n",
        "\n",
        "This will also further increase response speed, but may not necessairly result in fewer tokens used."
      ],
      "metadata": {
        "id": "tbQspzba2_GO"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "from pydantic import BaseModel, Field\n",
        "import orjson\n",
        "\n",
        "class write_python_function(BaseModel):\n",
        "    \"\"\"Writes a Python function based on the user input.\"\"\"\n",
        "    code: str = Field(description=\"Python code\")\n",
        "    efficient_code: str = Field(description=\"More efficient Python code than previously written\")"
      ],
      "metadata": {
        "id": "lFCLUODV28FZ"
      },
      "execution_count": 23,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "ai_struct = AIChat(api_key=api_key, console=False, model=model, params=params, save_messages=False)"
      ],
      "metadata": {
        "id": "6rTqftko4N2L"
      },
      "execution_count": 24,
      "outputs": []
    },
    {
      "cell_type": "code",
      "source": [
        "%%time\n",
        "response_structured = ai_struct(\"is_palindrome\", output_schema=write_python_function)\n",
        "\n",
        "# orjson.dumps preserves field order from the ChatGPT API\n",
        "print(orjson.dumps(response_structured, option=orjson.OPT_INDENT_2).decode())\n",
        "print(\"\\n\\n\")"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "4ielJLnz4e5U",
        "outputId": "d9b3f315-1840-4781-b922-f0cd889a4086"
      },
      "execution_count": 25,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "{\n",
            "  \"code\": \"def is_palindrome(s):\\n    return s == s[::-1]\",\n",
            "  \"efficient_code\": \"def is_palindrome(s):\\n    n = len(s)\\n    for i in range(n // 2):\\n        if s[i] != s[n - i - 1]:\\n            return False\\n    return True\"\n",
            "}\n",
            "\n",
            "\n",
            "\n",
            "CPU times: user 131 ms, sys: 1.39 ms, total: 133 ms\n",
            "Wall time: 1.67 s\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "As evident, the output is a `dict` so you'd just return the `efficient_code` field."
      ],
      "metadata": {
        "id": "M8sshigf53Nq"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "print(response_structured[\"efficient_code\"])"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "eIKI_8aq51RH",
        "outputId": "ab239077-3114-4b52-a127-0c5a93308627"
      },
      "execution_count": 26,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "def is_palindrome(s):\n",
            "    n = len(s)\n",
            "    for i in range(n // 2):\n",
            "        if s[i] != s[n - i - 1]:\n",
            "            return False\n",
            "    return True\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "ai_struct.total_length"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "KThchw1S7MwS",
        "outputId": "059aedeb-13c9-4802-a0cf-239ff7384722"
      },
      "execution_count": 27,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "161"
            ]
          },
          "metadata": {},
          "execution_count": 27
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "Token-wise it's about the same, but there's a significant speedup in generation for short queries such as these."
      ],
      "metadata": {
        "id": "iQcNrZcK7bn1"
      }
    },
    {
      "cell_type": "markdown",
      "source": [
        "Trying the other examples:"
      ],
      "metadata": {
        "id": "v2tfYs3a6oBy"
      }
    },
    {
      "cell_type": "code",
      "source": [
        "%%time\n",
        "response_structured = ai_struct(\"reverse string\", output_schema=write_python_function)\n",
        "print(response_structured[\"efficient_code\"])\n",
        "print(\"\\n\\n\")"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "ZrJtaW5m7Cdy",
        "outputId": "a74621e8-cebc-495b-adaa-bec926de36dd"
      },
      "execution_count": 28,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "def reverse_string(s):\n",
            "    return ''.join(reversed(s))\n",
            "\n",
            "\n",
            "\n",
            "CPU times: user 24.6 ms, sys: 250 µs, total: 24.9 ms\n",
            "Wall time: 1.37 s\n"
          ]
        }
      ]
    },
    {
      "cell_type": "code",
      "source": [
        "%%time\n",
        "response_structured = ai_struct(\"load and flip image horizontally\", output_schema=write_python_function)\n",
        "print(response_structured[\"efficient_code\"])\n",
        "print(\"\\n\\n\")"
      ],
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "JJxRji6h8Gww",
        "outputId": "45dbf068-b9b2-4d04-fccb-f988eda5538f"
      },
      "execution_count": 29,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "from PIL import Image\n",
            "\n",
            "def load_and_flip_image_horizontally(image_path):\n",
            "    return Image.open(image_path).transpose(Image.FLIP_LEFT_RIGHT)\n",
            "\n",
            "\n",
            "\n",
            "CPU times: user 15.4 ms, sys: 2.15 ms, total: 17.6 ms\n",
            "Wall time: 1.79 s\n"
          ]
        }
      ]
    },
    {
      "cell_type": "markdown",
      "source": [
        "## MIT License\n",
        "\n",
        "Copyright (c) 2023 Max Woolf\n",
        "\n",
        "Permission is hereby granted, free of charge, to any person obtaining a copy\n",
        "of this software and associated documentation files (the \"Software\"), to deal\n",
        "in the Software without restriction, including without limitation the rights\n",
        "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n",
        "copies of the Software, and to permit persons to whom the Software is\n",
        "furnished to do so, subject to the following conditions:\n",
        "\n",
        "The above copyright notice and this permission notice shall be included in all\n",
        "copies or substantial portions of the Software.\n",
        "\n",
        "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n",
        "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n",
        "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n",
        "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n",
        "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n",
        "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n",
        "SOFTWARE.\n"
      ],
      "metadata": {
        "id": "E-QaAsRXbhAj"
      }
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.9.12"
    },
    "orig_nbformat": 4,
    "colab": {
      "provenance": []
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}